#include <string.h>

#include "../Scheduler/Scheduler.h"
#include "../Checkpointer/PinCheckpointer.h"
#include "../HSGlobal.h"

extern std::vector<Scheduler *> Schedulers;
extern PinCheckpointer *ckptr;


/*Store size bytes of *value's contents into the address NVAddr in NV Mem*/
void STORE_NV_INTERNAL(void *value, unsigned long NVAddr, size_t size){

  std::vector<Scheduler *>::iterator i,e;
  for(i = Schedulers.begin(), e = Schedulers.end(); i != e; i++){

    (*i)->STORE_NV(value,NVAddr,size);

  }

}

/*Read size bytes from NV Mem addr NVAddr and put it in *buf*/
void LOAD_NV_INTERNAL(void *buf, unsigned long NVAddr, size_t size){
  
  std::vector<Scheduler *>::iterator i,e;
  for(i = Schedulers.begin(), e = Schedulers.end(); i != e; i++){

    (*i)->LOAD_NV(buf,NVAddr,size);

  }
}

void LD_CURR_CP_AFTER_RECOVERY_RTN_INTERNAL( void *recoveryRtn ){
  
  std::vector<Scheduler *>::iterator i,e;
  for(i = Schedulers.begin(), e = Schedulers.end(); i != e; i++){

    (*i)->LD_CURR_CP_AFTER_RECOVERY_RTN(recoveryRtn);

  }

}

void GET_RECOVERY_ROUTINE_INTERNAL( void *recoveryRtn ){
  
  std::vector<Scheduler *>::iterator i,e;
  for(i = Schedulers.begin(), e = Schedulers.end(); i != e; i++){

    (*i)->GET_RECOVERY_ROUTINE(recoveryRtn);

  }

}

unsigned long GET_DEATH_ID_INTERNAL(){
 
  std::vector<Scheduler *>::iterator i,e;
  for(i = Schedulers.begin(), e = Schedulers.end(); i != e; i++){

    (*i)->GET_DEATH_ID();

  }
  return 0;
}

void SET_DEATH_ID_INTERNAL(unsigned long id){
  
  std::vector<Scheduler *>::iterator i,e;
  for(i = Schedulers.begin(), e = Schedulers.end(); i != e; i++){

    (*i)->SET_DEATH_ID(id);

  }

}

bool GET_FAILURE_BIT_INTERNAL(){
  
  std::vector<Scheduler *>::iterator i,e;
  for(i = Schedulers.begin(), e = Schedulers.end(); i != e; i++){

    (*i)->GET_FAILURE_BIT();

  }
  return false;
}

void SET_FAILURE_BIT_INTERNAL(){
 
  std::vector<Scheduler *>::iterator i,e;
  for(i = Schedulers.begin(), e = Schedulers.end(); i != e; i++){

    (*i)->SET_FAILURE_BIT();

  }
}

void CLEAR_FAILURE_BIT_INTERNAL(){

  std::vector<Scheduler *>::iterator i,e;
  for(i = Schedulers.begin(), e = Schedulers.end(); i != e; i++){

    (*i)->CLEAR_FAILURE_BIT();

  }
}

void STORE_EXPLICIT_CHECKPOINT_INTERNAL( unsigned ckptNo ){

  //HSLOG(stderr,"STORE_EXPLICIT_CHECKPOINT_INTERNAL\n");
  
  unsigned long size = ckptr->getCP(ckptNo)->stackSize + 32;
  std::vector<Scheduler *>::iterator i,e;
  for(i = Schedulers.begin(), e = Schedulers.end(); i != e; i++){

    (*i)->STORE_EXPLICIT_CHECKPOINT(ckptNo, size);

  }


}

void LOAD_EXPLICIT_CHECKPOINT_INTERNAL( unsigned ckptNo ){

  //HSLOG(stderr,"LOAD_EXPLICIT_CHECKPOINT_INTERNAL\n");
  unsigned long size = ckptr->getCP(ckptNo)->stackSize + 32;
  std::vector<Scheduler *>::iterator i,e;
  for(i = Schedulers.begin(), e = Schedulers.end(); i != e; i++){

    (*i)->LOAD_EXPLICIT_CHECKPOINT(ckptNo, size);

  }
}

void LOAD_EXPLICIT_CHECKPOINT_SCHED( unsigned ckptNo ){

  LOAD_EXPLICIT_CHECKPOINT_INTERNAL( ckptNo );
  RestoreCheckPoint( ckptNo );

}

void TRY_BEGIN_INTERNAL( unsigned tryNum ){

  unsigned long size = ckptr->getCP(tryNum)->stackSize + 32;
  std::vector<Scheduler *>::iterator i,e;
  for(i = Schedulers.begin(), e = Schedulers.end(); i != e; i++){

    (*i)->SET_DEATH_ID(tryNum);
    (*i)->STORE_EXPLICIT_CHECKPOINT(tryNum, size);

  }

}

void TRY_END_INTERNAL( unsigned tryNum ){

  unsigned long size = ckptr->getCP(tryNum)->stackSize + 32;
  std::vector<Scheduler *>::iterator i,e;
  for(i = Schedulers.begin(), e = Schedulers.end(); i != e; i++){

    (*i)->SET_DEATH_ID(HSOUTOFBLOCK);
    (*i)->STORE_EXPLICIT_CHECKPOINT(tryNum, size);

  }

}

void LOAD_CURRENT_CHECKPOINT_INTERNAL(){

  std::vector<Scheduler *>::iterator i,e;
  for(i = Schedulers.begin(), e = Schedulers.end(); i != e; i++){

    (*i)->LOAD_CURRENT_CHECKPOINT();

  }
}


VOID replaceRuntimeFunctions(RTN rtn, VOID *v){
  if(strstr(RTN_Name(rtn).c_str(),"STORE_NV")){
    RTN_Open(rtn);
    RTN_Replace(rtn, (AFUNPTR)STORE_NV_INTERNAL);
    RTN_Close(rtn);
  }
  if(strstr(RTN_Name(rtn).c_str(),"LOAD_NV")){
    RTN_Open(rtn);
    RTN_Replace(rtn, 
                    
                   (AFUNPTR)LOAD_NV_INTERNAL);
    RTN_Close(rtn);
  }
  if(strstr(RTN_Name(rtn).c_str(),"LD_CURR_CP_AFTER_RECOVERY_RTN")){
    RTN_Open(rtn);
    RTN_Replace(rtn, 
                    
                   (AFUNPTR)LD_CURR_CP_AFTER_RECOVERY_RTN_INTERNAL); 
                   
    RTN_Close(rtn);
  }
  if(strstr(RTN_Name(rtn).c_str(),"GET_RECOVERY_ROUTINE")){
    RTN_Open(rtn);
    RTN_Replace(rtn, 
                    
                   (AFUNPTR)GET_RECOVERY_ROUTINE_INTERNAL); 
                   
    RTN_Close(rtn);
  }
  if(strstr(RTN_Name(rtn).c_str(),"GET_DEATH_ID")){
    RTN_Open(rtn);
    RTN_Replace(rtn, 
                    
                   (AFUNPTR)GET_DEATH_ID_INTERNAL); 
                   
    RTN_Close(rtn);
  }
  if(strstr(RTN_Name(rtn).c_str(),"SET_DEATH_ID")){
    RTN_Open(rtn);
    RTN_Replace(rtn, 
                    
                   (AFUNPTR)SET_DEATH_ID_INTERNAL); 
                   
    RTN_Close(rtn);
  }
  if(strstr(RTN_Name(rtn).c_str(),"GET_FAILURE_BIT")){
    RTN_Open(rtn);
    RTN_Replace(rtn, 
                    
                   (AFUNPTR)GET_FAILURE_BIT_INTERNAL); 
                   
    RTN_Close(rtn);
  }
  if(strstr(RTN_Name(rtn).c_str(),"SET_FAILURE_BIT")){
    RTN_Open(rtn);
    RTN_Replace(rtn, 
                    
                   (AFUNPTR)SET_FAILURE_BIT_INTERNAL); 
                   
    RTN_Close(rtn);
  }
  if(strstr(RTN_Name(rtn).c_str(),"CLEAR_FAILURE_BIT")){
    RTN_Open(rtn);
    RTN_Replace(rtn, 
                    
                   (AFUNPTR)CLEAR_FAILURE_BIT_INTERNAL); 
                   
    RTN_Close(rtn);
  }
  if(strstr(RTN_Name(rtn).c_str(),"STORE_EXPLICIT_CHECKPOINT") && !strstr(RTN_Name(rtn).c_str(),"STORE_EXPLICIT_CHECKPOINT_WITH_RECOVERY")){
    RTN_Open(rtn);
    RTN_Replace(rtn, 
                    
                   (AFUNPTR)STORE_EXPLICIT_CHECKPOINT_INTERNAL); 
                   
    RTN_Close(rtn);
  }
  if(strstr(RTN_Name(rtn).c_str(),"LOAD_EXPLICIT_CHECKPOINT")){
    RTN_Open(rtn);
    RTN_Replace(rtn, 
                    
                   (AFUNPTR)LOAD_EXPLICIT_CHECKPOINT_INTERNAL); 
                   
    RTN_Close(rtn);
  }
  if(strstr(RTN_Name(rtn).c_str(),"LOAD_CURRENT_CHECKPOINT")){
    RTN_Open(rtn);
    RTN_Replace(rtn, 
                    
                   (AFUNPTR)LOAD_CURRENT_CHECKPOINT_INTERNAL); 
                   
    RTN_Close(rtn);
  }
  if(strstr(RTN_Name(rtn).c_str(),"TRY_BEGIN")){
    RTN_Open(rtn);
    RTN_Replace(rtn, 
                    
                   (AFUNPTR)TRY_BEGIN_INTERNAL); 
                   
    RTN_Close(rtn);
  }
  if(strstr(RTN_Name(rtn).c_str(),"TRY_END")){
    RTN_Open(rtn);
    RTN_Replace(rtn, 
                    
                   (AFUNPTR)TRY_END_INTERNAL); 
                   
    RTN_Close(rtn);
  }
  if(strstr(RTN_Name(rtn).c_str(),"STORE_NV")){
    RTN_Open(rtn);
    RTN_Replace(rtn, 
                    
                   (AFUNPTR)STORE_NV_INTERNAL); 
                   
    RTN_Close(rtn);
  }
  if(strstr(RTN_Name(rtn).c_str(),"LOAD_NV")){
    RTN_Open(rtn);
    RTN_Replace(rtn, 
                    
                   (AFUNPTR)LOAD_NV_INTERNAL); 
                   
    RTN_Close(rtn);
  }
  
}

void initRuntime(){
  
  RTN_AddInstrumentFunction(replaceRuntimeFunctions, 0);

}
